package ch.ethz.fcl.metrobuzz.render.techniques.circos.version2;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.render.techniques.bezier.Bezier;
import ch.ethz.fcl.metrobuzz.render.techniques.circos.CircosDiagram;
import ch.ethz.fcl.metrobuzz.render.techniques.circos.CircosMatrix;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrobuzz.tools.MBTools;
import ch.ethz.fcl.metrofont.MetroFont;

/**
 * 
 * @author Zeng Wei
 * @email zeng@arch.ethz.ch
 * 
 *        Compare with version 1, indicate junction with flyover ring
 */

public class CircosDiagram2 extends CircosDiagram {
	int[] colorIndex;

	public CircosDiagram2(Point2D center, float radius, CircosMatrix data) {
		super(center, radius, data);

		data.postProcess();

		this.radius = radius;
		this.center = center;
		this.data = data;
	}

	public void render(MetroFont fm) {
		super.render(fm);
	}

	public void update() {
		gap = (float) Math.min(gap, Math.PI / (data.size() - 1) / 2);

		float[][] distri = data.getData();
		float[] outgoing = data.getOutgoing();
		float[] incoming = data.getIncoming();
		colorIndex = data.getColorIndex();

		Vector2d[] dirs = data.getDirections().getNonOverlapDirections(gap,
				renderAngle, minRenderAngle);
		int[][] outRenderOrder = data.getSubRenderOrder();
		int[] renderOrder = data.getRenderOrder();

		outgoingStartPoints = new Point2D[data.size()][data.size()];
		outgoingEndPoints = new Point2D[data.size()][data.size()];
		incomingStartPoints = new Point2D[data.size()][data.size()];
		incomingEndPoints = new Point2D[data.size()][data.size()];

		for (int i = 0; i < renderOrder.length; i++) {
			int renderIndex = renderOrder[i];

			float arcAngle = renderAngle
					* (outgoing[renderIndex] + incoming[renderIndex])
					/ (data.getTotalSum() - outgoing[0] - incoming[0]);

			MBTools.debug("index " + renderIndex + " sum "
					+ (outgoing[renderIndex] + incoming[renderIndex])
					+ " angle " + arcAngle * 180 / Math.PI, false);

			Vector2d startDir = MBTools
					.rotate(dirs[renderIndex], -arcAngle / 2);

			Vector2d dir = startDir;
			for (int j = 0; outRenderOrder[renderIndex] != null
					&& j < outRenderOrder[renderIndex].length; j++) {
				int sortedIndex = outRenderOrder[renderIndex][j];
				Point2D outgoingStart = center.getPointAt(dir, radius);
				float outgoingAngle = distri[renderIndex][sortedIndex]
						/ (outgoing[renderIndex] + incoming[renderIndex])
						* arcAngle;

				outgoingStartPoints[renderIndex][sortedIndex] = outgoingStart;

				dir = MBTools.rotate(dir, outgoingAngle);

				outgoingEndPoints[renderIndex][sortedIndex] = center
						.getPointAt(dir, radius);
			}

			float outgoingAngle = outgoing[renderIndex]
					/ (outgoing[renderIndex] + incoming[renderIndex])
					* arcAngle;

			MBTools.debug("index " + renderIndex + " outgoing "
					+ outgoing[renderIndex] + " angle " + outgoingAngle * 180
					/ Math.PI, false);
			// Vector2d incomingDir = MBTools.rotate(startDir, outgoingAngle);
			// vector2d incomingDir =

			for (int j = 0; outRenderOrder[renderIndex] != null
					&& j < outRenderOrder[renderIndex].length; j++) {
				int sortedIndex = outRenderOrder[renderIndex][j];
				Point2D incomingStart = center.getPointAt(dir, radius
						* arcElementRibbonGapRatio);
				float incomingAngle = distri[sortedIndex][renderIndex]
						/ (outgoing[renderIndex] + incoming[renderIndex])
						* arcAngle;

				incomingStartPoints[sortedIndex][renderIndex] = incomingStart;

				dir = MBTools.rotate(dir, incomingAngle);

				incomingEndPoints[sortedIndex][renderIndex] = center
						.getPointAt(dir, radius * arcElementRibbonGapRatio);
			}
		}

		for (int i = 1; i < data.size(); i++) {
			float ratioIncoming = flyoverRingRadius * flyoverRingOutRatio
					* flyoverRingRibbonGapRatio / radius;
			float ratioOutgoing = flyoverRingRadius * flyoverRingOutRatio
					/ (radius * arcElementRibbonGapRatio);
			incomingStartPoints[i][0] = center.getPointAt(
					outgoingStartPoints[i][0], ratioIncoming);
			incomingEndPoints[i][0] = center.getPointAt(
					outgoingEndPoints[i][0], ratioIncoming);
			outgoingStartPoints[0][i] = center.getPointAt(
					incomingStartPoints[0][i], ratioOutgoing);
			outgoingEndPoints[0][i] = center.getPointAt(
					incomingEndPoints[0][i], ratioOutgoing);
		}
	}

	protected void rendEclipse(int index, Point2D start, float radius,
			float angle) {
		if (angle < minRenderAngle)
			return;

		Vector2d dir = new Vector2d(start.getX() - center.getX(), start.getY()
				- center.getY());
		Point2D[] innerPoints = getEclipsePoints(radius, dir, angle);
		Point2D[] outerPoints = getEclipsePoints(radius * arcElementOutRatio,
				dir, angle);

		GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
		for (int i = 0; i < innerPoints.length; i++) {
			GL11.glVertex3f(innerPoints[i].getX(), innerPoints[i].getY(), 0);

			GL11.glVertex3f(outerPoints[i].getX(), outerPoints[i].getY(), 0);
		}
		GL11.glEnd();

		// render outline
		GL11.glLineWidth(1.0f);
		GL11.glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
		GL11.glBegin(GL11.GL_LINE_STRIP);
		MBGL.render(innerPoints[innerPoints.length - 1]);
		for (int i = innerPoints.length - 1; i >= 0; i--)
			MBGL.render(outerPoints[i]);
		GL11.glVertex3f(innerPoints[0].getX(), innerPoints[0].getY(), 0);
		GL11.glEnd();
	}

	@Override
	protected void renderArcElements() {
		for (int i = 1; i < data.size(); i++) {
			MBColorTable.assignColor(data.getColorIndex()[i], 1.0f);
			int[] outOrder = data.getSubRenderOrder()[i];

			float eclipseAngle = renderAngle
					* (data.getIncoming()[i] + data.getOutgoing()[i])
					/ (data.getTotalSum() - (data.getIncoming()[0] + data
							.getOutgoing()[0]));

			if (eclipseAngle < minRenderAngle)
				continue;

			rendEclipse(i, outgoingStartPoints[i][outOrder[0]], radius,
					eclipseAngle);

			for (int j = 0; j < outOrder.length; j++) {
				Point2D[] points = getEclipsePoints(
						incomingStartPoints[outOrder[j]][i],
						incomingEndPoints[outOrder[j]][i], radius);
				MBGL.render(points, GL11.GL_LINE_STRIP);
			}
		}
	}

	protected void fillHole() {
		for (int i = 1; i < data.size() && outgoingStartPoints != null; i++) {
			int[] outOrder = data.getSubRenderOrder()[i];
			for (int j = 0; j < outOrder.length; j++) {
				if (outOrder[j] == 0)
					continue;
				fillHole(i, outgoingStartPoints[i][outOrder[j]],
						outgoingEndPoints[i][outOrder[j]], radius, false);
			}

			int[] inOrder = data.getSubRenderOrder()[i];
			for (int j = 0; j < inOrder.length; j++) {
				float distribution = data.getData()[inOrder[j]][i];
				if (distribution
						/ ((data.getTotalSum() - (data.getIncoming()[0] + data
								.getOutgoing()[0])) - (data.getIncoming()[inOrder[j]] + data
								.getOutgoing()[inOrder[j]])) < minRenderAngle)
					continue;
				if (inOrder[j] == 0)
					continue;
				fillHole(i, incomingStartPoints[inOrder[j]][i],
						incomingEndPoints[inOrder[j]][i], radius
								* arcElementRibbonGapRatio, true);
			}

			fillHole(0, incomingStartPoints[i][0], incomingEndPoints[i][0],
					flyoverRingRadius * flyoverRingOutRatio * 1.1f, true);
		}
	}

	private void fillHole(int index, Point2D start, Point2D end, float radius,
			boolean renderOutline) {
		Point2D[] ctrlPoints = { start, end };

		Point2D[] eclipsePoints = getEclipsePoints(start, end, radius);
		Point2D[] linePoints = Bezier.getBezierSpline(eclipsePoints.length,
				ctrlPoints);

		MBColorTable.assignColor(data.getColorIndex()[index], 1.0f);
		if (index != 0) {
			GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
			for (int i = 0; i < eclipsePoints.length; i++) {
				GL11.glVertex3f(eclipsePoints[i].getX(),
						eclipsePoints[i].getY(), 0);
				GL11.glVertex3f(linePoints[i].getX(), linePoints[i].getY(), 0);
			}
			GL11.glEnd();
		}
		if (renderOutline) {
			GL11.glLineWidth(2.0f);
			GL11.glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
			GL11.glBegin(GL11.GL_LINE_STRIP);
			for (int i = 0; i < eclipsePoints.length; i++) {
				GL11.glVertex3f(eclipsePoints[i].getX(),
						eclipsePoints[i].getY(), 0);
			}
			GL11.glEnd();
		}
	}
}